////////////////////////////////////////////////////////////////////////////
//
//  Crytek Engine Source File.
//  Copyright (C), Crytek Studios, 2002.
// -------------------------------------------------------------------------
//  File name:   decals.cpp
//  Version:     v1.00
//  Created:     28/5/2001 by Vladimir Kajalin
//  Compilers:   Visual Studio.NET
//  Description: draw, create decals on the world
// -------------------------------------------------------------------------
//  History:
//
////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include <ICryAnimation.h>

#include "DecalManager.h"
#include "3dEngine.h"
#include <IStatObj.h>
#include "ObjMan.h"
#include "terrain.h"
#include "RenderMeshMerger.h"
#include "RenderMeshUtils.h"
#include "VoxMan.h"
#include "VisAreas.h"
#include "DecalRenderNode.h"

#ifndef RENDER_MESH_TEST_DISTANCE
	#define RENDER_MESH_TEST_DISTANCE (0.2f)
#endif

static const int MAX_ASSEMBLE_SIZE = 5;

CDecalManager::CDecalManager() 
{ 
  m_nCurDecal = 0; 
  memset(m_arrbActiveDecals,0,sizeof(m_arrbActiveDecals)); 
}

CDecalManager::~CDecalManager()
{
	CDecal::ResetStaticData();
}

bool CDecalManager::AdjustDecalPosition( CryEngineDecalInfo & DecalInfo, bool bMakeFatTest )
{
	Matrix34A objMat, objMatInv;
	Matrix33 objRot, objRotInv;

	CStatObj * pEntObject = (CStatObj *)DecalInfo.ownerInfo.GetOwner(objMat);
	if(!pEntObject || !pEntObject->GetRenderMesh() || !pEntObject->GetRenderTrisCount())
		return false;

	objRot = Matrix33(objMat);
	objRot.NoScale(); // No scale.
	objRotInv = objRot;
	objRotInv.Invert();

	float fWorldScale = objMat.GetColumn(0).GetLength(); // GetScale
	float fWorldScaleInv = 1.0f / fWorldScale;
	
	// transform decal into object space 
	objMatInv = objMat;
	objMatInv.Invert();

	// put into normal object space hit direction of projection
	Vec3 vOS_HitDir = objRotInv.TransformVector(DecalInfo.vHitDirection).GetNormalized();

	// put into position object space hit position
	Vec3 vOS_HitPos = objMatInv.TransformPoint(DecalInfo.vPos);
	vOS_HitPos -= vOS_HitDir*RENDER_MESH_TEST_DISTANCE*fWorldScaleInv;

  IMaterial * pMat = DecalInfo.ownerInfo.pRenderNode ? DecalInfo.ownerInfo.pRenderNode->GetMaterial() : NULL;

  Vec3 vOS_OutPos(0,0,0), vOS_OutNormal(0,0,0), vTmp;
  IRenderMesh * pRM = pEntObject->GetRenderMesh();

  AABB aabbRNode;
  pRM->GetBBox(aabbRNode.min, aabbRNode.max);
  Vec3 vOut(0,0,0);
  if(!Intersect::Ray_AABB(Ray(vOS_HitPos,vOS_HitDir), aabbRNode, vOut))
    return false;
  
	if (!pRM || !pRM->GetVerticesCount())
		return false;

	if (RayRenderMeshIntersection(pRM, vOS_HitPos, vOS_HitDir, vOS_OutPos, vOS_OutNormal, false, 0, pMat) )
  {
		// now check that none of decal sides run across edges
    Vec3 srcp = vOS_OutPos + 0.01f*fWorldScaleInv*vOS_OutNormal; /// Rise hit point a little bit above hit plane.
    Vec3 vDecalNormal = vOS_OutNormal;
		float fMaxHitDistance = 0.02f*fWorldScaleInv;

		// get decal directions
		Vec3 vRi(0,0,0), vUp(0,0,0);
		if(fabs(vOS_OutNormal.Dot(Vec3(0,0,1)))>0.999f)
		{ // horiz surface
			vRi = Vec3(0,1,0);
			vUp = Vec3(1,0,0);
		}
		else
		{
			vRi = vOS_OutNormal.Cross(Vec3(0,0,1));
			vRi.Normalize();
			vUp = vOS_OutNormal.Cross(vRi);
			vUp.Normalize();
		}

		vRi*=DecalInfo.fSize*0.65f;
		vUp*=DecalInfo.fSize*0.65f;

    if(	!bMakeFatTest || (
      RayRenderMeshIntersection(pRM, srcp+vUp, -vDecalNormal, vTmp, vTmp,true,fMaxHitDistance, pMat) &&
      RayRenderMeshIntersection(pRM, srcp-vUp, -vDecalNormal, vTmp, vTmp,true,fMaxHitDistance, pMat) &&
      RayRenderMeshIntersection(pRM, srcp+vRi, -vDecalNormal, vTmp, vTmp,true,fMaxHitDistance, pMat) &&
      RayRenderMeshIntersection(pRM, srcp-vRi, -vDecalNormal, vTmp, vTmp,true,fMaxHitDistance, pMat) ))
    {
      DecalInfo.vPos = objMat.TransformPoint(vOS_OutPos + vOS_OutNormal*0.001f*fWorldScaleInv);
      DecalInfo.vNormal = objRot.TransformVector(vOS_OutNormal);
      return true;
    }
  }
	return false;
}

struct HitPosInfo
{
	HitPosInfo() { memset(this,0,sizeof(HitPosInfo)); }
	Vec3 vPos,vNormal;
	float fDistance;	
};

int __cdecl CDecalManager__CmpHitPos(const void* v1, const void* v2)
{
	HitPosInfo * p1 = (HitPosInfo*)v1;
	HitPosInfo * p2 = (HitPosInfo*)v2;

	if(p1->fDistance > p2->fDistance)
		return 1;
	else if(p1->fDistance < p2->fDistance)
		return -1;

	return 0;
}

bool CDecalManager::RayRenderMeshIntersection(IRenderMesh * pRenderMesh, const Vec3 & vInPos, const Vec3 & vInDir, 
																							Vec3 & vOutPos, Vec3 & vOutNormal,bool bFastTest,float fMaxHitDistance,IMaterial * pMat)
{
	SRayHitInfo hitInfo;
	hitInfo.bUseCache = GetCVars()->e_DecalsHitCache!=0;
	hitInfo.bInFirstHit = bFastTest;
	hitInfo.inRay.origin = vInPos;
	hitInfo.inRay.direction = vInDir.GetNormalized();
	hitInfo.inReferencePoint = vInPos;
	hitInfo.fMaxHitDistance = fMaxHitDistance;
	bool bRes = CRenderMeshUtils::RayIntersection( pRenderMesh,hitInfo,pMat );
	vOutPos = hitInfo.vHitPos;
	vOutNormal = hitInfo.vHitNormal;
	return bRes;
}

bool CDecalManager::SpawnHierarchical( const CryEngineDecalInfo & rootDecalInfo, CDecal* pCallerManagedDecal )
{
  // decal on terrain or simple decal on always static object
  if(!rootDecalInfo.ownerInfo.pRenderNode)
		return Spawn(rootDecalInfo, pCallerManagedDecal);

  bool bSuccess = false;

  AABB decalBoxWS;
  float fSize = rootDecalInfo.fSize;
  decalBoxWS.max = rootDecalInfo.vPos+Vec3(fSize,fSize,fSize);
  decalBoxWS.min = rootDecalInfo.vPos-Vec3(fSize,fSize,fSize);

  for(int nEntitySlotId=0; nEntitySlotId<16; nEntitySlotId++)
  {
    CStatObj * _pStatObj = NULL;
    Matrix34A entSlotMatrix; entSlotMatrix.SetIdentity();
    if (_pStatObj = (CStatObj *)rootDecalInfo.ownerInfo.pRenderNode->GetEntityStatObj(nEntitySlotId, 0, &entSlotMatrix, true))
    {
			CryEngineDecalInfo decalInfo = rootDecalInfo;
			decalInfo.ownerInfo.nRenderNodeSlotId = nEntitySlotId;

			if (_pStatObj->m_nFlags & STATIC_OBJECT_COMPOUND)
      {
				// spawn decals on stat obj sub objects
				int nLastSubId = decalInfo.ownerInfo.nRenderNodeSlotSubObjectId;
				if (decalInfo.ownerInfo.nRenderNodeSlotSubObjectId < 0)
				{
					decalInfo.ownerInfo.nRenderNodeSlotSubObjectId = 0;
					nLastSubId = _pStatObj->GetSubObjectCount() - 1;
				}
				for (; decalInfo.ownerInfo.nRenderNodeSlotSubObjectId <= nLastSubId; decalInfo.ownerInfo.nRenderNodeSlotSubObjectId++)
        {
					if (IStatObj* pSubStatObj = decalInfo.ownerInfo.GetOwner(entSlotMatrix))
					{
						AABB decalBoxLocal = AABB::CreateTransformedAABB(entSlotMatrix.GetInverted(), decalBoxWS);
            if (Overlap::AABB_AABB(decalBoxLocal, pSubStatObj->GetAABB()))
            {
							bSuccess |= Spawn(decalInfo, pCallerManagedDecal);
            }
          }
        }
      }
      else
      {
				AABB decalBoxLocal = AABB::CreateTransformedAABB(entSlotMatrix.GetInverted(), decalBoxWS);
        if (Overlap::AABB_AABB(decalBoxLocal, _pStatObj->GetAABB()))
        {
          decalInfo.ownerInfo.nRenderNodeSlotSubObjectId = -1; // no childs
					bSuccess |= Spawn(decalInfo, pCallerManagedDecal);
        }
      }
    }
    else if(ICharacterInstance * pChar = rootDecalInfo.ownerInfo.pRenderNode->GetEntityCharacter(nEntitySlotId, &entSlotMatrix))
    { // spawn decals on CGA components
      ISkeletonPose* pSkeletonPose = pChar->GetISkeletonPose();
      uint32 numJoints = pSkeletonPose->GetJointCount();
			CryEngineDecalInfo decalInfo = rootDecalInfo;
      decalInfo.ownerInfo.nRenderNodeSlotId = nEntitySlotId;

			if (rootDecalInfo.ownerInfo.nRenderNodeSlotSubObjectId>=0)
			{
        decalInfo.ownerInfo.nRenderNodeSlotSubObjectId = rootDecalInfo.ownerInfo.nRenderNodeSlotSubObjectId % PARTID_CGA;
				bSuccess |= Spawn(decalInfo, pCallerManagedDecal);
			}
			else
      // spawn decal on every sub-object intersecting decal bbox
      for(uint32 nJointId=0; nJointId<numJoints; nJointId++)
      {
        IStatObj * pStatObj = pSkeletonPose->GetStatObjOnJoint(nJointId);

        if(pStatObj && !(pStatObj->GetFlags()&STATIC_OBJECT_HIDDEN) && pStatObj->GetRenderMesh())
        {      
          assert(!pStatObj->GetSubObjectCount());

          Matrix34 tm34 = entSlotMatrix*Matrix34(pSkeletonPose->GetAbsJointByID(nJointId));
          AABB objBoxWS = AABB::CreateTransformedAABB(tm34, pStatObj->GetAABB());
          //				DrawBBox(objBoxWS);
          //			DrawBBox(decalBoxWS);
          if(Overlap::AABB_AABB(objBoxWS, decalBoxWS))
          {
            decalInfo.ownerInfo.nRenderNodeSlotSubObjectId = nJointId;
						bSuccess |= Spawn(decalInfo, pCallerManagedDecal);
          }
        }
      }
    }

    if(rootDecalInfo.ownerInfo.pRenderNode->GetRenderNodeType() == eERType_VoxelObject)
    {
      CryEngineDecalInfo decalInfo = rootDecalInfo;
      decalInfo.ownerInfo.nRenderNodeSlotId = 0;
      decalInfo.ownerInfo.nRenderNodeSlotSubObjectId = -1; // no childs
			bSuccess |= Spawn(decalInfo, pCallerManagedDecal);
      break;
    }
  }

  return bSuccess;
}

bool CDecalManager::Spawn( CryEngineDecalInfo DecalInfo, CDecal* pCallerManagedDecal )
{
	FUNCTION_PROFILER_3DENGINE;

  Vec3 vCamPos = GetSystem()->GetViewCamera().GetPosition();

	// do not spawn if too far
	float fZoom = GetObjManager() ? GetObjManager()->m_fZoomFactor : 1.f;
	float fDecalDistance = DecalInfo.vPos.GetDistance(vCamPos);
  if( !pCallerManagedDecal && (fDecalDistance > Get3DEngine()->GetMaxViewDistance() || fDecalDistance*fZoom>DecalInfo.fSize*ENTITY_DECAL_DIST_FACTOR*3.f) )
    return false;

	int overlapCount(0);
	int targetSize(0);
	int overlapIds[MAX_ASSEMBLE_SIZE];

	// do not spawn new decals if they could overlap the existing and similar ones
  if(!pCallerManagedDecal && !GetCVars()->e_DecalsOverlapping && DecalInfo.fSize && !DecalInfo.bSkipOverlappingTest)
  {
    for(int i=0; i<DECAL_COUNT; i++)
    {
			if(m_arrbActiveDecals[i])
			{
				// skip overlapping check if decals are very different in size
				if((m_arrDecals[i].m_iAssembleSize>0) == DecalInfo.bAssemble)
				{
					float fSizeRatio = m_arrDecals[i].m_fWSSize / DecalInfo.fSize;
					if(((m_arrDecals[i].m_iAssembleSize>0) || (fSizeRatio > 0.5f && fSizeRatio < 2.f)) && m_arrDecals[i].m_nGroupId != DecalInfo.nGroupId)
					{
						float fDist = m_arrDecals[i].m_vWSPos.GetSquaredDistance(DecalInfo.vPos);
						if(fDist < sqr(m_arrDecals[i].m_fWSSize * 0.5f + DecalInfo.fSize * 0.5f) && (DecalInfo.vNormal.Dot(m_arrDecals[i].m_vFront)>0.f))
						{
							if(DecalInfo.bAssemble && m_arrDecals[i].m_iAssembleSize<MAX_ASSEMBLE_SIZE)
							{
								if(overlapCount<MAX_ASSEMBLE_SIZE)
								{
									overlapIds[overlapCount] = i;
									overlapCount++;
								}
								else
								{
									m_arrbActiveDecals[i] = false;
								}
							}
							else
								return true;
						}
					}
				}
      }
    }
  }

	float fAssembleSizeModifier(1.0f);
	if(DecalInfo.bAssemble)
	{
		Vec3 avgPos(0.0f,0.0f,0.0f);
		int validAssembles(0);
		for(int i=0; i<overlapCount; i++)
		{
			int id = overlapIds[i];

			float fDist = m_arrDecals[id].m_vWSPos.GetSquaredDistance(DecalInfo.vPos);
			float minDist = sqr(m_arrDecals[id].m_fWSSize * 0.4f);
			if(fDist>minDist)
			{
				avgPos += m_arrDecals[id].m_vWSPos;
				targetSize += m_arrDecals[id].m_iAssembleSize;
				validAssembles++;
			}
		}

		if(overlapCount && !validAssembles)
			return true;

		for(int i=0; i<overlapCount; i++)
		{
			int id = overlapIds[i];
			m_arrbActiveDecals[id] = false;
		}

		++validAssembles;
		++targetSize;
		avgPos += DecalInfo.vPos;

		if(targetSize>1)
		{
			avgPos /= float(validAssembles);
			DecalInfo.vPos = avgPos;
			targetSize = min(targetSize,MAX_ASSEMBLE_SIZE);

			const float	sizetable[MAX_ASSEMBLE_SIZE] = {1.0f, 1.5f, 2.3f, 3.5f, 3.5f};
			const char sValue=(48+targetSize);
			strncat(DecalInfo.szMaterialName , &sValue, 1);
			fAssembleSizeModifier = sizetable[targetSize-1];
		}
	}


	if(GetCVars()->e_Decals > 1)
		DrawSphere(DecalInfo.vPos,DecalInfo.fSize);

	// update lifetime for near decals under control by the decal manager
	if( !pCallerManagedDecal )
	{
		if(DecalInfo.fSize > 1 && GetCVars()->e_DecalsNeighborMaxLifeTime)
		{ // force near decals to fade faster
			float fCurrTime = GetTimer()->GetCurrTime();
			for(int i=0; i<DECAL_COUNT; i++)
				if(m_arrbActiveDecals[i] && m_arrDecals[i].m_nGroupId != DecalInfo.nGroupId)
				{
					if(m_arrDecals[i].m_vWSPos.GetSquaredDistance(DecalInfo.vPos) < sqr(m_arrDecals[i].m_fWSSize / 1.5f + DecalInfo.fSize / 2.0f))
						if((m_arrDecals[i]).m_fLifeBeginTime < fCurrTime-0.1f)
							if(m_arrDecals[i].m_fLifeTime > GetCVars()->e_DecalsNeighborMaxLifeTime)
                if(m_arrDecals[i].m_fLifeTime < 10000) // decals spawn by cut scenes need to stay
  								m_arrDecals[i].m_fLifeTime = GetCVars()->e_DecalsNeighborMaxLifeTime;
				}
		}

		// loop position in array
		m_nCurDecal=(m_nCurDecal+1)&(DECAL_COUNT-1);
		//if(m_nCurDecal>=DECAL_COUNT)
		//	m_nCurDecal=0;
	}

	// create reference to decal which is to be filled 
	CDecal& newDecal( pCallerManagedDecal ? *pCallerManagedDecal : m_arrDecals[ m_nCurDecal ] );

	newDecal.m_bDeferred = DecalInfo.bDeferred;

	newDecal.m_iAssembleSize = targetSize;
	// free old pRM
	newDecal.FreeRenderData();

  newDecal.m_nGroupId = DecalInfo.nGroupId;

	// get material if specified
	newDecal.m_pMaterial = 0;

  if( DecalInfo.szMaterialName && DecalInfo.szMaterialName[0] != '0' )
	{
		newDecal.m_pMaterial = GetMatMan()->LoadMaterial( DecalInfo.szMaterialName, false,true );
		if( !newDecal.m_pMaterial )
		{
			newDecal.m_pMaterial = GetMatMan()->LoadMaterial( "Materials/Decals/Default",true,true );
			newDecal.m_pMaterial->AddRef();
			Warning( "CDecalManager::Spawn: Specified decal material \"%s\" not found!\n", DecalInfo.szMaterialName );
		}
	}
  else
    Warning( "CDecalManager::Spawn: Decal material name is not specified" );

	newDecal.m_sortPrio = DecalInfo.sortPrio;

	// set up user defined decal basis if provided
	bool useDefinedUpRight( false );
	Vec3 userDefinedUp;
	Vec3 userDefinedRight;
	if( DecalInfo.pExplicitRightUpFront )
	{
		userDefinedRight = DecalInfo.pExplicitRightUpFront->GetColumn( 0 );
		userDefinedUp = DecalInfo.pExplicitRightUpFront->GetColumn( 1 );
		DecalInfo.vNormal = DecalInfo.pExplicitRightUpFront->GetColumn( 2 );
		useDefinedUpRight = true;
	}

	// just in case
	DecalInfo.vNormal.NormalizeSafe();

	// remember object we need to follow
	newDecal.m_ownerInfo.nRenderNodeSlotId = DecalInfo.ownerInfo.nRenderNodeSlotId;
	newDecal.m_ownerInfo.nRenderNodeSlotSubObjectId = DecalInfo.ownerInfo.nRenderNodeSlotSubObjectId;

	newDecal.m_vWSPos = DecalInfo.vPos;
	newDecal.m_fWSSize = DecalInfo.fSize * fAssembleSizeModifier;

	// If owner entity and object is specified - make decal use entity geometry
	float _fObjScale = 1.f;

  Matrix34A _objMat;
  Matrix33 worldRot;
  IStatObj * pStatObj = DecalInfo.ownerInfo.GetOwner(_objMat);
  if (pStatObj)
  {
    worldRot = Matrix33(_objMat);
    _objMat.Invert();
  }

  float fWrapMinSize = GetFloatCVar(e_DecalsDefferedDynamicMinSize);

  bool bSpawnOnVegetationWithBending = false;
  if(DecalInfo.ownerInfo.pRenderNode && DecalInfo.ownerInfo.pRenderNode->GetRenderNodeType() == eERType_Vegetation && DecalInfo.fSize>fWrapMinSize && !DecalInfo.bDeferred)
  {
//    CVegetation * pVeg = (CVegetation*)DecalInfo.ownerInfo.pRenderNode;
//    StatInstGroup & rGroup = GetObjManager()->m_lstStaticTypes[pVeg->m_nObjectTypeID];
  //  if(rGroup.fBending>0)
      bSpawnOnVegetationWithBending = true;

    // Check owner material ID (need for decals vertex modifications)
    SRayHitInfo hitInfo;
    memset(&hitInfo,0,sizeof(hitInfo));
    Vec3 vHitPos = _objMat.TransformPoint(DecalInfo.vPos);
    // put hit normal into the object space
    Vec3 vRayDir = DecalInfo.vHitDirection.GetNormalized()*worldRot;
    hitInfo.inReferencePoint = vHitPos;
    hitInfo.inRay.origin = vHitPos - vRayDir*4.0f;
    hitInfo.inRay.direction = vRayDir;
    hitInfo.bInFirstHit = false;
    hitInfo.bUseCache = true;
    if (pStatObj->RayIntersection(hitInfo, 0))
      newDecal.m_ownerInfo.nMatID = hitInfo.nHitMatID;
  }

	if(DecalInfo.ownerInfo.pRenderNode && DecalInfo.ownerInfo.nRenderNodeSlotId>=0 && (DecalInfo.fSize>fWrapMinSize || pCallerManagedDecal || bSpawnOnVegetationWithBending) && !DecalInfo.bDeferred)
	{ 
		newDecal.m_eDecalType = eDecalType_OS_OwnersVerticesUsed;

    IRenderMesh * pSourceRenderMesh = NULL;
   
    if(pStatObj)
		  pSourceRenderMesh = pStatObj->GetRenderMesh();
    else if(DecalInfo.ownerInfo.pRenderNode->GetRenderNodeType() == eERType_VoxelObject)
    {
      CVoxelObject * pVox = (CVoxelObject*)DecalInfo.ownerInfo.pRenderNode;
      pSourceRenderMesh = pVox->GetRenderMesh(0);
      _objMat = pVox->GetMatrix();
      _objMat.Invert();
    }

    if(!pSourceRenderMesh)
      return false;

		// transform decal into object space 
		Matrix33 objRotInv = Matrix33(_objMat);
		objRotInv.NoScale();

		if( useDefinedUpRight )
		{
			userDefinedRight = objRotInv.TransformVector( userDefinedRight ).GetNormalized();
			userDefinedUp = objRotInv.TransformVector( userDefinedUp ).GetNormalized();
			assert( fabsf( DecalInfo.vNormal.Dot( -DecalInfo.vHitDirection.GetNormalized() ) - 1.0f ) < 1e-4f );
		}

    // make decals smaller but longer if hit direction is near perpendicular to surface normal
    float fSizeModificator = 0.25f + 0.75f*fabs(DecalInfo.vHitDirection.GetNormalized().Dot(DecalInfo.vNormal));

		// put into normal object space hit direction of projection
		DecalInfo.vNormal = -objRotInv.TransformVector((DecalInfo.vHitDirection - DecalInfo.vNormal*0.25f).GetNormalized());			

		if(!DecalInfo.vNormal.IsZero())
			DecalInfo.vNormal.Normalize();

		// put into position object space hit position
		DecalInfo.vPos = _objMat.TransformPoint(DecalInfo.vPos);

		// find object scale
    float fObjScale = 1.f;
		Vec3 vTest(0,0,1.f);
		vTest = _objMat.TransformVector(vTest);
		fObjScale = 1.f/vTest.len();

		if(fObjScale<0.01f)
			return false;

		// transform size into object space
		DecalInfo.fSize/=fObjScale;

		DecalInfo.fSize *= (DecalInfo.bAssemble ? fAssembleSizeModifier : fSizeModificator);

		if(DecalInfo.bForceEdge)
		{
			SRayHitInfo hitInfo;
			hitInfo.bUseCache = GetCVars()->e_DecalsHitCache!=0;
			hitInfo.bInFirstHit = false;
			hitInfo.inRay.origin = DecalInfo.vPos + DecalInfo.vNormal;
			hitInfo.inRay.direction = -DecalInfo.vNormal;
			hitInfo.inReferencePoint = DecalInfo.vPos + DecalInfo.vNormal;
			hitInfo.inRetTriangle = true;
			bool bRes = CRenderMeshUtils::RayIntersection( pSourceRenderMesh, hitInfo, pStatObj ? pStatObj->GetMaterial() : NULL );

			MoveToEdge(pSourceRenderMesh, DecalInfo.fSize, hitInfo.vHitPos, hitInfo.vHitNormal, hitInfo.vTri0, hitInfo.vTri1, hitInfo.vTri2);
			DecalInfo.vPos = hitInfo.vHitPos;
			DecalInfo.vNormal = hitInfo.vHitNormal;
		}

		// make decal geometry
    newDecal.m_pRenderMesh = MakeBigDecalRenderMesh(pSourceRenderMesh, DecalInfo.vPos, DecalInfo.fSize, DecalInfo.vNormal, newDecal.m_pMaterial, pStatObj ? pStatObj->GetMaterial() : NULL);

		if(!newDecal.m_pRenderMesh)
			return false; // no geometry found
	}	
	else if(!DecalInfo.ownerInfo.pRenderNode && DecalInfo.ownerInfo.pDecalReceivers && (DecalInfo.fSize>fWrapMinSize || pCallerManagedDecal) && !DecalInfo.bDeferred)
	{
		newDecal.m_eDecalType = eDecalType_WS_Merged;

		assert(!newDecal.m_pRenderMesh);

		// put into normal hit direction of projection
		DecalInfo.vNormal = -DecalInfo.vHitDirection;			
		if(!DecalInfo.vNormal.IsZero())
			DecalInfo.vNormal.Normalize();

    Vec3 vSize(DecalInfo.fSize*1.333f,DecalInfo.fSize*1.333f,DecalInfo.fSize*1.333f);
    AABB decalAABB(DecalInfo.vPos-vSize,DecalInfo.vPos+vSize);

		// build list of affected brushes
		PodArray<SRenderMeshInfoInput> lstRMI;
		for(int nObj=0; nObj<DecalInfo.ownerInfo.pDecalReceivers->Count(); nObj++)
		{
			IRenderNode * pDecalOwner = DecalInfo.ownerInfo.pDecalReceivers->Get(nObj)->pNode;
			Matrix34A objMat;		
      if(IStatObj * pEntObject = pDecalOwner->GetEntityStatObj(DecalInfo.ownerInfo.nRenderNodeSlotId, 0, &objMat))
      {
			  SRenderMeshInfoInput rmi;
			  rmi.pMesh = pEntObject->GetRenderMesh();
			  rmi.pMat = pEntObject->GetMaterial();
			  rmi.mat = objMat;

        if(rmi.pMesh)
        {
          AABB transAABB = AABB::CreateTransformedAABB(rmi.mat,pEntObject->GetAABB());
          if(Overlap::AABB_AABB(decalAABB,transAABB))
            lstRMI.Add(rmi);
        }
        else
        if(int nSubObjCount = pEntObject->GetSubObjectCount())
        { // multi sub objects
          for(int nSubObj=0; nSubObj<nSubObjCount; nSubObj++)
          {
            IStatObj::SSubObject * pSubObj = pEntObject->GetSubObject(nSubObj);
            if(pSubObj->pStatObj)
            {
              rmi.pMesh = pSubObj->pStatObj->GetRenderMesh();
              rmi.pMat = pSubObj->pStatObj->GetMaterial();
              rmi.mat = objMat*pSubObj->tm;
              if(rmi.pMesh)
              {
                AABB transAABB = AABB::CreateTransformedAABB(rmi.mat,pSubObj->pStatObj->GetAABB());
                if(Overlap::AABB_AABB(decalAABB,transAABB))
                  lstRMI.Add(rmi);
              }
            }
          }
        }
      }
      else if(pDecalOwner->GetRenderNodeType() == eERType_VoxelObject)
      {
        SRenderMeshInfoInput rmi;
        CVoxelObject * pVoxObj = (CVoxelObject *)pDecalOwner;
        rmi.pMesh = pVoxObj->GetRenderMesh(0);
        if(rmi.pMesh)
        {
          rmi.pMat = GetTerrain()->GetMaterial();
          rmi.mat = pVoxObj->GetMatrix();
          AABB transAABB = pDecalOwner->GetBBox();
          if(Overlap::AABB_AABB(decalAABB,transAABB))
            lstRMI.Add(rmi);
        }
      }
		}

		if(!lstRMI.Count()) 
			return false;

		SDecalClipInfo DecalClipInfo;
		DecalClipInfo.vPos = DecalInfo.vPos;
		DecalClipInfo.fRadius = DecalInfo.fSize;
		DecalClipInfo.vProjDir = DecalInfo.vNormal;

		PodArray<SRenderMeshInfoOutput> outRenderMeshes;
		CRenderMeshMerger Merger;
		SMergeInfo info;
		info.sMeshName = "MergedDecal";
		info.sMeshType = "MergedDecal";
		info.pDecalClipInfo = &DecalClipInfo;
    info.vResultOffset = DecalInfo.vPos;
		newDecal.m_pRenderMesh = Merger.MergeRenderMeshes(lstRMI.GetElements(), lstRMI.Count(), outRenderMeshes,info);

		if(!newDecal.m_pRenderMesh)
			return false; // no geometry found

		assert(newDecal.m_pRenderMesh->GetChunks().Count()==1);
	}
	else
  if( DecalInfo.ownerInfo.pRenderNode && 
		  DecalInfo.ownerInfo.pRenderNode->GetRenderNodeType() == eERType_RenderProxy && 
		  DecalInfo.ownerInfo.nRenderNodeSlotId>=0 )
	{ 
		newDecal.m_eDecalType = eDecalType_OS_SimpleQuad;

		Matrix34A objMat;

		// transform decal from world space into entity space
		IStatObj * pEntObject = DecalInfo.ownerInfo.GetOwner(objMat);
		if(!pEntObject)
			return false;
		assert(pEntObject);
		objMat.Invert();

		if( useDefinedUpRight )
		{
			userDefinedRight = objMat.TransformVector( userDefinedRight ).GetNormalized();
			userDefinedUp = objMat.TransformVector( userDefinedUp ).GetNormalized();
			assert( fabsf( DecalInfo.vNormal.Dot( -DecalInfo.vHitDirection.GetNormalized() ) - 1.0f ) < 1e-4f );
		}

		DecalInfo.vNormal = objMat.TransformVector(DecalInfo.vNormal).GetNormalized();
		DecalInfo.vPos = objMat.TransformPoint(DecalInfo.vPos);

		// find object scale
		if(DecalInfo.ownerInfo.pRenderNode->GetRenderNodeType()==eERType_Vegetation)
      _fObjScale = ((CVegetation*)(DecalInfo.ownerInfo.pRenderNode))->GetScale();
		else
		{
			Vec3 vTest(0,0,1.f);
			vTest = objMat.TransformVector(vTest);
			_fObjScale = 1.f/vTest.len();
		}

    DecalInfo.fSize/=_fObjScale;
	}
	else 
	{
		CTerrain* pTerrain = GetTerrain();
		if(!DecalInfo.preventDecalOnGround && DecalInfo.fSize>(fWrapMinSize*2.f) && !DecalInfo.ownerInfo.pRenderNode && 
			 (DecalInfo.vPos.z - pTerrain->GetZApr(DecalInfo.vPos.x,DecalInfo.vPos.y, false, GetDefSID())) < DecalInfo.fSize && !DecalInfo.bDeferred)
		{
			newDecal.m_eDecalType = eDecalType_WS_OnTheGround;

			int nUnitSize = CTerrain::GetHeightMapUnitSize();
			int x1 = int(DecalInfo.vPos.x-DecalInfo.fSize)/nUnitSize*nUnitSize-nUnitSize;
			int x2 = int(DecalInfo.vPos.x+DecalInfo.fSize)/nUnitSize*nUnitSize+nUnitSize;
			int y1 = int(DecalInfo.vPos.y-DecalInfo.fSize)/nUnitSize*nUnitSize-nUnitSize;
			int y2 = int(DecalInfo.vPos.y+DecalInfo.fSize)/nUnitSize*nUnitSize+nUnitSize;

			for(int x=x1; x<=x2; x+=CTerrain::GetHeightMapUnitSize())
			for(int y=y1; y<=y2; y+=CTerrain::GetHeightMapUnitSize())
				if(pTerrain->GetHole(x,y,GetDefSID()))
					return false;
		}
		else
			newDecal.m_eDecalType = eDecalType_WS_SimpleQuad;
		
		DecalInfo.ownerInfo.pRenderNode = NULL;
	}

  // spawn
	if( !useDefinedUpRight )
	{
    if(DecalInfo.vNormal.Dot(Vec3(0,0,1))>0.999f)
    { // floor
      newDecal.m_vRight = Vec3(0,1,0);
      newDecal.m_vUp    = Vec3(-1,0,0);
    }
		else if(DecalInfo.vNormal.Dot(Vec3(0,0,-1))>0.999f)
		{ // ceil
			newDecal.m_vRight = Vec3(1,0,0);
			newDecal.m_vUp    = Vec3(0,-1,0);
		}
		else if(!DecalInfo.vNormal.IsZero())
		{
			newDecal.m_vRight = DecalInfo.vNormal.Cross(Vec3(0,0,1));
			newDecal.m_vRight.Normalize();
			newDecal.m_vUp    = DecalInfo.vNormal.Cross(newDecal.m_vRight);
			newDecal.m_vUp.Normalize();
		}

		// rotate vectors
		if(!DecalInfo.vNormal.IsZero())
		{
			AngleAxis rotation(DecalInfo.fAngle,DecalInfo.vNormal);
			newDecal.m_vRight = rotation*newDecal.m_vRight;
			newDecal.m_vUp		= rotation*newDecal.m_vUp;
		}
	}
	else
	{
		newDecal.m_vRight = userDefinedRight;
		newDecal.m_vUp = userDefinedUp;
	}

	newDecal.m_vFront		= DecalInfo.vNormal;

	newDecal.m_vPos = DecalInfo.vPos;
	newDecal.m_vPos += DecalInfo.vNormal * 0.001f / _fObjScale;

	newDecal.m_fSize = DecalInfo.fSize;
  newDecal.m_fLifeTime = DecalInfo.fLifeTime*GetCVars()->e_DecalsLifeTimeScale;
  assert(!DecalInfo.pIStatObj); // not used -> not supported
	newDecal.m_vAmbient = Get3DEngine()->GetAmbientColorFromPosition(newDecal.m_vWSPos);

	newDecal.m_ownerInfo.pRenderNode = DecalInfo.ownerInfo.pRenderNode;
	if (DecalInfo.ownerInfo.pRenderNode)
		DecalInfo.ownerInfo.pRenderNode->m_nInternalFlags |= IRenderNode::DECAL_OWNER;

	newDecal.m_fGrowTime = DecalInfo.fGrowTime;
  newDecal.m_fGrowTimeAlpha = DecalInfo.fGrowTimeAlpha;
	newDecal.m_fLifeBeginTime = GetTimer()->GetCurrTime();

  if(DecalInfo.pIStatObj && !pCallerManagedDecal)
  {
		//assert(!"Geometry decals neede to be re-debugged");
    DecalInfo.pIStatObj->AddRef();
  }

	if( !pCallerManagedDecal )
	{
		m_arrbActiveDecals[ m_nCurDecal ] = true;
		++m_nCurDecal;
	}

#ifdef _DEBUG
	if( newDecal.m_ownerInfo.pRenderNode )
	{
		int n = sizeof( newDecal.m_decalOwnerEntityClassName );
		strncpy( newDecal.m_decalOwnerEntityClassName, newDecal.m_ownerInfo.pRenderNode->GetEntityClassName(), n );
		newDecal.m_decalOwnerEntityClassName[n-1] = '\0';		
		n = sizeof( newDecal.m_decalOwnerName );
		strncpy( newDecal.m_decalOwnerName, newDecal.m_ownerInfo.pRenderNode->GetName(), n );
		newDecal.m_decalOwnerEntityClassName[n-1] = '\0';		
		newDecal.m_decalOwnerType = newDecal.m_ownerInfo.pRenderNode->GetRenderNodeType();
	}
	else
	{
		newDecal.m_decalOwnerEntityClassName[0] = '\0';
		newDecal.m_decalOwnerName[0] = '\0';
		newDecal.m_decalOwnerType = eERType_NotRenderNode;
	}
#endif

	return true;
}

void CDecalManager::Update( const float fFrameTime )
{
	CryPrefetch(&m_arrbActiveDecals[0]);
	CryPrefetch(&m_arrbActiveDecals[128]);
	CryPrefetch(&m_arrbActiveDecals[256]);
	CryPrefetch(&m_arrbActiveDecals[384]);

  for(int i=0; i<DECAL_COUNT; i++)
	{
	  if(m_arrbActiveDecals[i])
		{
			IRenderNode * pRenderNode = m_arrDecals[i].m_ownerInfo.pRenderNode;
			if(m_arrDecals[i].Update(m_arrbActiveDecals[i],fFrameTime))
				if(pRenderNode && m_arrTempUpdatedOwners.Find(pRenderNode)<0)
					m_arrTempUpdatedOwners.Add(pRenderNode);
		}
	}

	for(int i=0; i<m_arrTempUpdatedOwners.Count(); i++)
	{
		m_arrTempUpdatedOwners[i]->m_nInternalFlags &= ~IRenderNode::UPDATE_DECALS;
	}
	
	m_arrTempUpdatedOwners.Clear();
}


void CDecalManager::Render(int nEngineFrameID)
{
	FUNCTION_PROFILER_3DENGINE;

  if(!GetCVars()->e_Decals || !GetObjManager())
    return;

	float fCurrTime = GetTimer()->GetCurrTime();
	float fZoom = GetObjManager()->m_fZoomFactor;
	float fWaterLevel = m_p3DEngine->GetWaterLevel();

	static int nLastUpdateStreamingPrioriryRoundId = 0;
	bool bPrecacheMaterial = nLastUpdateStreamingPrioriryRoundId != GetObjManager()->m_nUpdateStreamingPrioriryRoundId;
	nLastUpdateStreamingPrioriryRoundId = GetObjManager()->m_nUpdateStreamingPrioriryRoundId;

	static int nLastUpdateStreamingPrioriryRoundIdFast = 0;
	bool bPrecacheMaterialFast = nLastUpdateStreamingPrioriryRoundIdFast != GetObjManager()->m_nUpdateStreamingPrioriryRoundIdFast;
	nLastUpdateStreamingPrioriryRoundIdFast = GetObjManager()->m_nUpdateStreamingPrioriryRoundIdFast;

	// draw
  for(int i=0; i<DECAL_COUNT; i++)
  if(m_arrbActiveDecals[i])
  {
		CDecal * pDecal = &m_arrDecals[i];
    pDecal->m_vWSPos = pDecal->GetWorldPosition();
    float fDist = GetCamera().GetPosition().GetDistance(pDecal->m_vWSPos)*fZoom;
    float fMaxViewDist = pDecal->m_fWSSize*ENTITY_DECAL_DIST_FACTOR*3.0f;
		if(fDist < fMaxViewDist)
			if(GetCamera().IsSphereVisible_F(Sphere(pDecal->m_vWSPos, pDecal->m_fWSSize)))
		{
      bool bAfterWater = CObjManager::IsAfterWater(pDecal->m_vWSPos,GetCamera().GetPosition(),fWaterLevel);
			if( pDecal->m_pMaterial )
			{
				if(!m_nRenderStackLevel)
				{
					if(bPrecacheMaterialFast && (fDist < GetFloatCVar(e_StreamPredictionMinFarZoneDistance)))
					{
						if(CMatInfo * pMatInfo = (CMatInfo *)(IMaterial*)pDecal->m_pMaterial)
							pMatInfo->PrecacheMaterial(fDist,NULL, true);
					}
					
					if(bPrecacheMaterial)
					{
						pDecal->m_vAmbient = Get3DEngine()->GetAmbientColorFromPosition(pDecal->m_vWSPos);
						if(CMatInfo * pMatInfo = (CMatInfo *)(IMaterial*)pDecal->m_pMaterial)
							pMatInfo->PrecacheMaterial(fDist,NULL, false);
					}
				}

        // TODO: take entity orientation into account
        Vec3 vSize(pDecal->m_fWSSize,pDecal->m_fWSSize,pDecal->m_fWSSize);
        AABB aabb(pDecal->m_vWSPos-vSize,pDecal->m_vWSPos+vSize);
        
        uint32 nDLMask = 0;

				if(!pDecal->m_bDeferred)
				{
					IRenderNode * pRN = pDecal->m_ownerInfo.pRenderNode;
					if(COctreeNode * pNode = (COctreeNode*)(pRN ? pRN->m_pOcNode : NULL))
						nDLMask = Get3DEngine()->BuildLightMask( aabb, pNode->GetAffectingLights(nEngineFrameID), (CVisArea*)pRN->GetEntityVisArea(), (pRN->GetRndFlags()&ERF_OUTDOORONLY)!=0 );
					else
						nDLMask = Get3DEngine()->BuildLightMask(aabb, nEngineFrameID);
				}

        float fDistFading = SATURATE((1.f - fDist/fMaxViewDist)*DIST_FADING_FACTOR);

				pDecal->Render( fCurrTime, bAfterWater, nDLMask, fDistFading, fDist );

				if(GetCVars()->e_Decals>1)
				{
          Vec3 vCenter = pDecal->m_vWSPos;
          AABB aabbCenter(vCenter-vSize*0.05f, vCenter+vSize*0.05f);

          DrawBBox(aabb);
          DrawBBox(aabbCenter, Col_Yellow);

          Vec3 vNormal( Vec3( pDecal->m_vUp ).Cross( -pDecal->m_vRight ).GetNormalized() );

          Matrix34A objMat;
          IStatObj * pEntObject = pDecal->m_ownerInfo.GetOwner(objMat);
          if(pEntObject)
            vNormal = objMat.TransformVector(vNormal).GetNormalized();

          DrawLine(vCenter, vCenter+vNormal*pDecal->m_fWSSize);

          if(pDecal->m_pRenderMesh)
          {
            pDecal->m_pRenderMesh->GetBBox(aabb.min,aabb.max);
            DrawBBox(aabb, Col_Red);
          }
				}
			}
		}
  }
}

//////////////////////////////////////////////////////////////////////////
void CDecalManager::OnEntityDeleted(IRenderNode * pRenderNode)
{
  FUNCTION_PROFILER_3DENGINE;

	// remove decals of this entity
	for(int i=0; i<DECAL_COUNT; i++)
	{
		if(m_arrbActiveDecals[i])
		{
			if(m_arrDecals[i].m_ownerInfo.pRenderNode == pRenderNode)
			{
        if(GetCVars()->e_Decals == 2)
        {
          CDecal & decal = m_arrDecals[i];
          Vec3 vPos = decal.GetWorldPosition();
          char * szOwnerName = "none";
#ifdef _DEBUG
          szOwnerName = decal.m_decalOwnerName;
#endif
          PrintMessage("Debug: C3DEngine::OnDecalDeleted: Pos=(%.1f,%.1f,%.1f) Size=%.2f DecalMaterial=%s OwnerName=%s", 
            vPos.x, vPos.y, vPos.z, decal.m_fSize, decal.m_pMaterial ? decal.m_pMaterial->GetName() : "none", szOwnerName);
        }

				m_arrbActiveDecals[i] = false;
				m_arrDecals[i].FreeRenderData();
			}
		}
	}

  // update decal render nodes
  PodArray<IRenderNode*> lstObjects;
  
  Get3DEngine()->GetObjectsByTypeGlobal(lstObjects,eERType_Decal,NULL);

  if (Get3DEngine()->GetVisAreaManager())
    Get3DEngine()->GetVisAreaManager()->GetObjectsByType(lstObjects,eERType_Decal,NULL);

  for(int i=0; i<lstObjects.Count(); i++)
    ((CDecalRenderNode*)lstObjects[i])->RequestUpdate();
}

//////////////////////////////////////////////////////////////////////////
void CDecalManager::OnRenderMeshDeleted(IRenderMesh * pRenderMesh)
{
	// remove decals of this entity
	for(int i=0; i<DECAL_COUNT; i++)
	{
		if(m_arrbActiveDecals[i])
		{
			if(	
				(m_arrDecals[i].m_ownerInfo.pRenderNode && (
				m_arrDecals[i].m_ownerInfo.pRenderNode->GetRenderMesh(0) == pRenderMesh ||
				m_arrDecals[i].m_ownerInfo.pRenderNode->GetRenderMesh(1) == pRenderMesh ||
				m_arrDecals[i].m_ownerInfo.pRenderNode->GetRenderMesh(2) == pRenderMesh)) 
				||
				(m_arrDecals[i].m_pRenderMesh && m_arrDecals[i].m_pRenderMesh->GetVertexContainer() == pRenderMesh) 
				)
			{
				m_arrbActiveDecals[i] = false;
				m_arrDecals[i].FreeRenderData();
//				PrintMessage("CDecalManager::OnRenderMeshDeleted succseed");
			}
		}
	}
}

void CDecalManager::MoveToEdge(IRenderMesh * pRM, const float fRadius, Vec3 & vOutPos, Vec3 & vOutNormal, const Vec3 & vTri0, const Vec3 & vTri1, const Vec3 & vTri2 )
{
	FUNCTION_PROFILER_3DENGINE;

	AABB boxRM;
	pRM->GetBBox(boxRM.min,boxRM.max);
	Sphere sp(vOutPos, fRadius);
	if(!Overlap::Sphere_AABB(sp, boxRM))
		return;

	// get position offset and stride
	int nPosStride = 0;
	byte * pPos  = pRM->GetPosPtr(nPosStride, FSL_READ);

	uint16 *pInds = pRM->GetIndexPtr(FSL_READ);

	if(!pPos || !pInds)
		return;

	int nInds = pRM->GetIndicesCount();

//	if(nInds>6000)
	//	return; // skip insane objects

	assert(nInds%3 == 0);

	if(!vOutNormal.IsZero())
		vOutNormal.Normalize();
	else
		return;

	float bestDot = 2.0f;
	Vec3 bestNormal(ZERO);
	Vec3 bestPoint(ZERO);

	// render tris
	PodArray<CRenderChunk>& Chunks = pRM->GetChunks();
	for(int nChunkId=0; nChunkId<Chunks.Count(); nChunkId++)
	{
    CRenderChunk * pChunk = &Chunks[nChunkId];
		if(pChunk->m_nMatFlags & MTL_FLAG_NODRAW || !pChunk->pRE)
			continue;

		int nLastIndexId = pChunk->nFirstIndexId + pChunk->nNumIndices;

		for(int i=pChunk->nFirstIndexId; i<nLastIndexId; i+=3)
		{
			assert(pInds[i+0] < pChunk->nFirstVertId+pChunk->nNumVerts);
			assert(pInds[i+1] < pChunk->nFirstVertId+pChunk->nNumVerts);
			assert(pInds[i+2] < pChunk->nFirstVertId+pChunk->nNumVerts);
			assert(pInds[i+0] >= pChunk->nFirstVertId);
			assert(pInds[i+1] >= pChunk->nFirstVertId);
			assert(pInds[i+2] >= pChunk->nFirstVertId);

			// get tri vertices
			Vec3 v0 = (*(Vec3*)&pPos[nPosStride*pInds[i+0]]);
			Vec3 v1 = (*(Vec3*)&pPos[nPosStride*pInds[i+1]]);
			Vec3 v2 = (*(Vec3*)&pPos[nPosStride*pInds[i+2]]);

			bool first = false;
			bool second = false;
			bool third = false;

			if(	v0==vTri0 || v0==vTri1 || v0==vTri2)
				first = true;
			else if(v1==vTri0 || v1==vTri1 || v1==vTri2)
				second = true;
			else if(	v2==vTri0 || v2==vTri1 || v2==vTri2)
				third = true;

			if(first || second || third)
			{
				// get triangle normal
				Vec3 vNormal = (v1-v0).Cross(v2-v0).GetNormalized();

				float testDot = vNormal.Dot(vOutNormal);
				if(testDot < bestDot)
				{
					bestDot = testDot;
					bestNormal = vNormal;
					if(first)
						bestPoint = v0;
					else if(second)
						bestPoint = v1;
					else if(third)
						bestPoint = v2;
				}

			}
		}
		
	}

	if(bestDot<1.0f)
	{
		vOutNormal = (bestNormal + vOutNormal).GetNormalized();
		vOutPos.x = bestPoint.x;
		vOutPos.y = bestPoint.y;
	}
}

void CDecalManager::FillBigDecalIndices(IRenderMesh * pRM, Vec3 vPos, float fRadius, Vec3 vProjDirIn, PodArray<uint16> * plstIndices, IMaterial * pMat, AABB & meshBBox, float & texelAreaDensity)
{
	FUNCTION_PROFILER_3DENGINE;

  AABB boxRM;
  pRM->GetBBox(boxRM.min,boxRM.max);

  Sphere sp(vPos, fRadius);
  if(!Overlap::Sphere_AABB(sp, boxRM))
    return;

	HWVSphere hwSphere(sp);

  // get position offset and stride
  int nInds = pRM->GetIndicesCount();

  if(nInds > GetCVars()->e_DecalsMaxTrisInObject*3)
    return; // skip insane objects

  CDecalRenderNode::m_nFillBigDecalIndicesCounter++;

  struct MeshLock
  {
	  MeshLock(IRenderMesh* m = 0) : mesh(m)
	  {
		  if( m ) m->LockForThreadAccess();
	  }
	  ~MeshLock()
	  {
		  if( mesh )
		  {
			  mesh->UnlockStream(VSF_GENERAL);
			  mesh->UnlockIndexStream();
			  mesh->UnLockForThreadAccess();
		  }
	  }
  private:
	  IRenderMesh* mesh;
  };

  MeshLock rmLock(pRM);

	int nPosStride = 0;
	byte * pPos   = pRM->GetPosPtr(nPosStride, FSL_READ);
	uint16 *pInds = pRM->GetIndexPtr(FSL_READ);

	if(!pInds || !pPos)
		return;

	assert(nInds%3 == 0);

	plstIndices->Clear();

	bool bPointProj(vProjDirIn.IsZeroFast());

	if(!bPointProj)
		vProjDirIn.Normalize();

  if(!pMat)
    return;

	plstIndices->PreAllocate(16);

	hwvec3 vProjDir = HWVLoadVecUnaligned(&vProjDirIn);
  
  int usedTrianglesTotal = 0;
	
  PodArray<CRenderChunk>& Chunks = pRM->GetChunks();
  
  // disabled the decal optimization since it lead to very big allocations on PS3(over 100MB)










































































  {
		hwvec3 meshBBoxMin	= HWVLoadVecUnaligned(&meshBBox.min);
		hwvec3 meshBBoxMax	= HWVLoadVecUnaligned(&meshBBox.max);

		SIMDFConstant(fEpsilon, 0.001f);

		const int kNumChunks = Chunks.Count();

		if(bPointProj)
		{ 		
			hwvec3 hwvPos				= HWVLoadVecUnaligned(&vPos);

			for(int nChunkId=0; nChunkId < kNumChunks; nChunkId++)
			{
				CRenderChunk * pChunk = &Chunks[nChunkId];

				PrefetchLine(&Chunks[nChunkId + 1], 0);
				PrefetchLine(&pInds[pChunk->nFirstIndexId], 0);

				if(pChunk->m_nMatFlags & MTL_FLAG_NODRAW || !pChunk->pRE)
					continue;

				const SShaderItem &shaderItem = pMat->GetShaderItem(pChunk->m_nMatID);

				if(!shaderItem.m_pShader || !shaderItem.m_pShaderResources)
					continue;

				if(shaderItem.m_pShader->GetFlags() & (EF_NODRAW | EF_DECAL))
					continue;

				PrefetchLine(plstIndices->GetElements(), 0);

				int usedTriangles = 0;
				int nLastIndexId = pChunk->nFirstIndexId + pChunk->nNumIndices;

				int i=pChunk->nFirstIndexId;
				
				int iPosIndex0 = nPosStride*pInds[i+0];
				int iPosIndex1 = nPosStride*pInds[i+1];
				int iPosIndex2 = nPosStride*pInds[i+2];

				for(; i<nLastIndexId; i+=3)
				{
					assert(pInds[i+0] < pChunk->nFirstVertId+pChunk->nNumVerts);
					assert(pInds[i+1] < pChunk->nFirstVertId+pChunk->nNumVerts);
					assert(pInds[i+2] < pChunk->nFirstVertId+pChunk->nNumVerts);
					assert(pInds[i+0] >= pChunk->nFirstVertId);
					assert(pInds[i+1] >= pChunk->nFirstVertId);
					assert(pInds[i+2] >= pChunk->nFirstVertId);

					PrefetchLine(&pInds[i], 128);
					
					int iNextPosIndex0 = 0;
					int iNextPosIndex1 = 0;
					int iNextPosIndex2 = 0;

					if(i+5 < nLastIndexId)
					{
						iNextPosIndex0 = nPosStride*pInds[i+3];
						iNextPosIndex1 = nPosStride*pInds[i+4];
						iNextPosIndex2 = nPosStride*pInds[i+5];
					
						PrefetchLine(&pPos[iNextPosIndex0], 0);
						PrefetchLine(&pPos[iNextPosIndex1], 0);
						PrefetchLine(&pPos[iNextPosIndex2], 0);
					}


					// get tri vertices
					const hwvec3 v0 = HWVLoadVecUnaligned(reinterpret_cast<Vec3*>(&pPos[iPosIndex0]));
					const hwvec3 v1 = HWVLoadVecUnaligned(reinterpret_cast<Vec3*>(&pPos[iPosIndex1]));
					const hwvec3 v2 = HWVLoadVecUnaligned(reinterpret_cast<Vec3*>(&pPos[iPosIndex2]));

					// test the face
					hwvec3 v0v1Diff		= HWVSub(v0, v1);
					hwvec3 v2v1Diff		= HWVSub(v2, v1);
					hwvec3 vPosv0Diff = HWVSub(hwvPos, v0);

					hwvec3 vCrossResult = HWVCross(v0v1Diff, v2v1Diff);

					simdf fDot = HWV3Dot(vPosv0Diff, vCrossResult);

					if(SIMDFGreaterThan( fDot, fEpsilon ))
					{
						if(Overlap::HWVSphere_TriangleFromPoints(hwSphere, v0, v1, v2))
						{
							plstIndices->AddList(&pInds[i], 3);

							hwvec3 triBBoxMax1 = HWVMax(v1, v0);
							hwvec3 triBBoxMax2 = HWVMax(meshBBoxMax, v2);

							hwvec3 triBBoxMin1 = HWVMin(v1, v0);						
							hwvec3 triBBoxMin2 = HWVMin(meshBBoxMin, v2);

							meshBBoxMax = HWVMax(triBBoxMax1, triBBoxMax2);
							meshBBoxMax = HWVMin(triBBoxMin1, triBBoxMin2);

							usedTriangles++;
						}
					}

					iPosIndex0 = iNextPosIndex0;
					iPosIndex1 = iNextPosIndex1;
					iPosIndex2 = iNextPosIndex2;
				}

				if (pChunk->m_texelAreaDensity > 0.0f && pChunk->m_texelAreaDensity != (float)UINT_MAX)
				{
					texelAreaDensity += usedTriangles*pChunk->m_texelAreaDensity;
					usedTrianglesTotal += usedTriangles;
				}
			}
		}
		else
		{
			for(int nChunkId=0; nChunkId < kNumChunks; nChunkId++)
			{
				CRenderChunk * pChunk = &Chunks[nChunkId];

        if(nChunkId + 1 < kNumChunks)
				  PrefetchLine(&Chunks[nChunkId + 1], 0);
				PrefetchLine(&pInds[pChunk->nFirstIndexId], 0);

				if(pChunk->m_nMatFlags & MTL_FLAG_NODRAW || !pChunk->pRE)
					continue;

				const SShaderItem &shaderItem = pMat->GetShaderItem(pChunk->m_nMatID);

				if(!shaderItem.m_pShader || !shaderItem.m_pShaderResources)
					continue;

				if(shaderItem.m_pShader->GetFlags() & (EF_NODRAW | EF_DECAL))
					continue;

				PrefetchLine(plstIndices->GetElements(), 0);

				int usedTriangles = 0;
				const int nLastIndexId = pChunk->nFirstIndexId + pChunk->nNumIndices;
				const int nLastValidIndexId = nLastIndexId - 1;

				int i=pChunk->nFirstIndexId;
				
				int iNextPosIndex0 = 0;
				int iNextPosIndex1 = 0;
				int iNextPosIndex2 = 0;

				if(i+5 < nLastIndexId)
				{
					iNextPosIndex0 = nPosStride*pInds[i+3];
					iNextPosIndex1 = nPosStride*pInds[i+4];
					iNextPosIndex2 = nPosStride*pInds[i+5];
				
					PrefetchLine(&pPos[iNextPosIndex0], 0);
					PrefetchLine(&pPos[iNextPosIndex1], 0);
					PrefetchLine(&pPos[iNextPosIndex2], 0);
				}

				register hwvec3 v0Next = HWVLoadVecUnaligned(reinterpret_cast<Vec3*>(&pPos[nPosStride*pInds[i+0]]));
				register hwvec3 v1Next = HWVLoadVecUnaligned(reinterpret_cast<Vec3*>(&pPos[nPosStride*pInds[i+1]]));
				register hwvec3 v2Next = HWVLoadVecUnaligned(reinterpret_cast<Vec3*>(&pPos[nPosStride*pInds[i+2]]));

				const int nLastIndexToUse = nLastIndexId - 3;

				for(; i<nLastIndexToUse; i+=3)
				{
					assert(pInds[i+0] < pChunk->nFirstVertId+pChunk->nNumVerts);
					assert(pInds[i+1] < pChunk->nFirstVertId+pChunk->nNumVerts);
					assert(pInds[i+2] < pChunk->nFirstVertId+pChunk->nNumVerts);
					assert(pInds[i+0] >= pChunk->nFirstVertId);
					assert(pInds[i+1] >= pChunk->nFirstVertId);
					assert(pInds[i+2] >= pChunk->nFirstVertId);

					const int iLookaheadIdx = min_branchless(i+8, nLastValidIndexId);
					const int iPrefetchIndex2 = nPosStride*pInds[iLookaheadIdx];

					// get tri vertices
					const register hwvec3 v0 = v0Next;
					const register hwvec3 v1 = v1Next;
					const register hwvec3 v2 = v2Next;

					//Need to prefetch further ahead
					byte * pPrefetch = &pPos[iPrefetchIndex2];
 					PrefetchLine(pPrefetch, 0);

					v0Next = HWVLoadVecUnaligned(reinterpret_cast<Vec3*>(&pPos[iNextPosIndex0]));
					
					// get triangle normal
 					hwvec3 v1v0Diff = HWVSub(v1, v0);
 					hwvec3 v2v0Diff	= HWVSub(v2, v0);

					v1Next = HWVLoadVecUnaligned(reinterpret_cast<Vec3*>(&pPos[iNextPosIndex1]));

					hwvec3 vNormal	= HWVCross(v1v0Diff, v2v0Diff);
					simdf fDot = HWV3Dot(vNormal, vProjDir);

					v2Next = HWVLoadVecUnaligned(reinterpret_cast<Vec3*>(&pPos[iNextPosIndex2]));

					// test the face
					if(SIMDFGreaterThan(fDot, fEpsilon))
					{
						if(Overlap::HWVSphere_TriangleFromPoints(hwSphere, v0, v1, v2))
						{
							plstIndices->AddList(&pInds[i], 3);

							hwvec3 triBBoxMax1 = HWVMax(v1, v0);
							hwvec3 triBBoxMax2 = HWVMax(meshBBoxMax, v2);

							hwvec3 triBBoxMin1 = HWVMin(v1, v0);						
							hwvec3 triBBoxMin2 = HWVMin(meshBBoxMin, v2);

							meshBBoxMax = HWVMax(triBBoxMax1, triBBoxMax2);
							meshBBoxMax = HWVMin(triBBoxMin1, triBBoxMin2);

							usedTriangles++;
						}
					}

					iNextPosIndex0 = nPosStride*pInds[iLookaheadIdx-2];
					iNextPosIndex1 = nPosStride*pInds[iLookaheadIdx-1];
					iNextPosIndex2 = iPrefetchIndex2;				
				}

				const register hwvec3 v0 = v0Next;
				const register hwvec3 v1 = v1Next;
				const register hwvec3 v2 = v2Next;

				// get triangle normal
				hwvec3 v1v0Diff = HWVSub(v1, v0);
				hwvec3 v2v0Diff	= HWVSub(v2, v0);
				hwvec3 vNormal	= HWVCross(v1v0Diff, v2v0Diff);
				simdf fDot = HWV3Dot(vNormal, vProjDir);

				// test the face
				if(SIMDFGreaterThan(fDot, fEpsilon))
				{
					if(Overlap::HWVSphere_TriangleFromPoints(hwSphere, v0, v1, v2))
					{
						plstIndices->AddList(&pInds[i], 3);

						hwvec3 triBBoxMax1 = HWVMax(v1, v0);
						hwvec3 triBBoxMax2 = HWVMax(meshBBoxMax, v2);

						hwvec3 triBBoxMin1 = HWVMin(v1, v0);						
						hwvec3 triBBoxMin2 = HWVMin(meshBBoxMin, v2);

						meshBBoxMax = HWVMax(triBBoxMax1, triBBoxMax2);
						meshBBoxMax = HWVMin(triBBoxMin1, triBBoxMin2);

// 						triBBoxMax = HWVMax(triBBoxMax, v2);
// 						triBBoxMin = HWVMin(triBBoxMin, v2);
// 
// 						meshBBoxMax = HWVMax(triBBoxMax, meshBBoxMax);
// 						meshBBoxMin = HWVMin(triBBoxMin, meshBBoxMin);

						usedTriangles++;
					}
				}


				if (pChunk->m_texelAreaDensity > 0.0f && pChunk->m_texelAreaDensity != (float)UINT_MAX)
				{
					texelAreaDensity += usedTriangles*pChunk->m_texelAreaDensity;
					usedTrianglesTotal += usedTriangles;
				}
			}
		}

		HWVSaveVecUnaligned(&meshBBox.max, meshBBoxMax);
		HWVSaveVecUnaligned(&meshBBox.min, meshBBoxMin);
	}

	if (usedTrianglesTotal != 0)
	{
		texelAreaDensity = texelAreaDensity/usedTrianglesTotal;
	}
}


IRenderMesh * CDecalManager::MakeBigDecalRenderMesh(IRenderMesh * pSourceRenderMesh, Vec3 vPos, float fRadius, Vec3 vProjDir, IMaterial* pDecalMat, IMaterial* pSrcMat)
{
  if (!pSourceRenderMesh || pSourceRenderMesh->GetVerticesCount()==0)
    return 0;

	// make indices of this decal
	PodArray<uint16> lstIndices;		
	
  AABB meshBBox(vPos,vPos);
	float texelAreaDensity = 0.0f;

	if (pSourceRenderMesh && pSourceRenderMesh->GetVerticesCount())
		FillBigDecalIndices(pSourceRenderMesh, vPos, fRadius, vProjDir, &lstIndices, pSrcMat, meshBBox, texelAreaDensity);

	if(!lstIndices.Count())
		return 0;

	// make fake vert buffer with one vertex // todo: remove this
  SVF_P3S_C4B_T2S EmptyVert;

	IRenderMesh* pRenderMesh = GetRenderer()->CreateRenderMeshInitialized( &EmptyVert, 1, eVF_P3S_C4B_T2S,
		lstIndices.GetElements(), lstIndices.Count(), prtTriangleList, "BigDecalOnStatObj","BigDecal", eRMT_Static, 1, 0, 0, 0, false, false, 0 );
	pRenderMesh->SetVertexContainer(pSourceRenderMesh);
	pRenderMesh->SetChunk( pDecalMat, 0, pSourceRenderMesh->GetVerticesCount(), 0, lstIndices.Count(), texelAreaDensity );
  pRenderMesh->SetBBox(meshBBox.min, meshBBox.max);

  return pRenderMesh;
}

/*
IRenderMesh * CDecalManager::MakeBigDecalRenderMesh(IRenderMesh * pSourceRenderMesh, Vec3 vPos, float fRadius, Vec3 vProjDir, int nTexID)
{
if(!pSourceRenderMesh || pSourceRenderMesh->GetSysVertCount()==0)
return 0;

// make indices of this decal
PodArray<uint16> lstIndices;		

if(pSourceRenderMesh && pSourceRenderMesh->GetSysVertBuffer() && pSourceRenderMesh->GetSysVertCount())
FillBigDecalIndices(pSourceRenderMesh, vPos, fRadius, vProjDir, &lstIndices);

if(!lstIndices.Count())
return 0;

// make fake vert buffer with one vertex // todo: remove this
PodArray<struct_VERTEX_FORMAT_P3F_N> VertBuffer;
VertBuffer.PreAllocate(pSourceRenderMesh->GetSysVertCount());
int nPosStride=0, nNormStride=0;
void * pPos = pSourceRenderMesh->GetPosPtr(nPosStride);
void * pNorm = pSourceRenderMesh->GetPosPtr(nNormStride);
for(int i=0; i<pSourceRenderMesh->GetSysVertCount(); i++)
{
struct_VERTEX_FORMAT_P3F_N vert;
vert.xyz = *(Vec3*)((uint8*)pPos+nPosStride*i);
vert.normal = *(Vec3*)((uint8*)pNorm+nNormStride*i);
vert.normal = vert.normal*5;
VertBuffer.Add(vert);
}

IRenderMesh * pRenderMesh = GetRenderer()->CreateRenderMeshInitialized(
VertBuffer.GetElements(), VertBuffer.Count(), VERTEX_FORMAT_P3F_N,
lstIndices.GetElements(), lstIndices.Count(),
R_PRIMV_TRIANGLES, "BigDecal", eRMT_Static, 1, nTexID);

pRenderMesh->SetChunk(m_pShader_Decal_VP, 0, pSourceRenderMesh->GetSysVertCount(), 0, lstIndices.Count());
pRenderMesh->SetMaterial(m_pShader_Decal_VP,nTexID);

return pRenderMesh;
}
*/
void CDecalManager::GetMemoryUsage(ICrySizer*pSizer) const
{
	pSizer->Add (*this);
}

void CDecalManager::DeleteDecalsInRange( AABB * pAreaBox, IRenderNode * pEntity )
{
  if(GetCVars()->e_Decals>1 && pAreaBox)
    DrawBBox(*pAreaBox);

	for(int i=0; i<DECAL_COUNT; i++)
  {
		if(!m_arrbActiveDecals[i])
      continue;

    if(pEntity && (pEntity != m_arrDecals[i].m_ownerInfo.pRenderNode))
      continue;

    if(pAreaBox)
    {
      Vec3 vPos = m_arrDecals[i].GetWorldPosition();
      Vec3 vSize(m_arrDecals[i].m_fWSSize, m_arrDecals[i].m_fWSSize, m_arrDecals[i].m_fWSSize);
      AABB decalBox(vPos-vSize, vPos+vSize);
      if( !Overlap::AABB_AABB(*pAreaBox, decalBox) )
        continue;
    }

    if(m_arrDecals[i].m_eDecalType != eDecalType_WS_OnTheGround)
		  m_arrbActiveDecals[i] = false;

		m_arrDecals[i].FreeRenderData();

    if(GetCVars()->e_Decals == 2)
    {
      CDecal & decal = m_arrDecals[i];
      PrintMessage("Debug: CDecalManager::DeleteDecalsInRange: Pos=(%.1f,%.1f,%.1f) Size=%.2f DecalMaterial=%s", 
        decal.m_vPos.x, decal.m_vPos.y, decal.m_vPos.z, decal.m_fSize, decal.m_pMaterial ? decal.m_pMaterial->GetName() : "none");
    }
  }
}

void CDecalManager::Serialize( TSerialize ser )
{
	ser.BeginGroup("StaticDecals");

	if(ser.IsReading())
		Reset();

	uint32 dwDecalCount=0;

  for(uint32 dwI=0; dwI<DECAL_COUNT; dwI++)
	  if(m_arrbActiveDecals[dwI])
			dwDecalCount++;

	ser.Value("DecalCount",dwDecalCount);

	if(ser.IsWriting())
	{
	  for(uint32 _dwI=0; _dwI<DECAL_COUNT; _dwI++)
			if(m_arrbActiveDecals[_dwI])
			{
				CDecal &ref = m_arrDecals[_dwI];

				ser.BeginGroup("Decal");
				ser.Value("Pos",ref.m_vPos);
				ser.Value("Right",ref.m_vRight);
				ser.Value("Up",ref.m_vUp);
				ser.Value("Front",ref.m_vFront);
				ser.Value("Size",ref.m_fSize);
				ser.Value("WSPos",ref.m_vWSPos);
				ser.Value("WSSize",ref.m_fWSSize);
				ser.Value("fLifeTime",ref.m_fLifeTime);

				// serialize material, handle legacy decals with textureID converted to material created at runtime
				string matName( "" );
				if( ref.m_pMaterial && ref.m_pMaterial->GetName() )
					matName = ref.m_pMaterial->GetName();
				ser.Value( "MatName", matName );

//			TODO:  IStatObj *		m_pStatObj;												// only if real 3d object is used as decal ()
				ser.Value("vAmbient",ref.m_vAmbient);
//			TODO:  IRenderNode * m_pDecalOwner;										// transformation parent object (0 means parent is the world)
        ser.Value("nRenderNodeSlotId",ref.m_ownerInfo.nRenderNodeSlotId);
        ser.Value("nRenderNodeSlotSubObjectId",ref.m_ownerInfo.nRenderNodeSlotSubObjectId);
				
				int nDecalType = (int)ref.m_eDecalType;
				ser.Value("eDecalType", nDecalType);

				ser.Value("fGrowTime",ref.m_fGrowTime);
        ser.Value("fGrowTimeAlpha",ref.m_fGrowTimeAlpha);
				ser.Value("fLifeBeginTime",ref.m_fLifeBeginTime);

				bool bBigDecalUsed = ref.IsBigDecalUsed();

				ser.Value("bBigDecal",bBigDecalUsed);

				// m_arrBigDecalRMs[] will be created on the fly so no need load/save it

				if(bBigDecalUsed)
				{
					for(uint32 dwI=0;dwI<sizeof(ref.m_arrBigDecalRMCustomData)/sizeof(ref.m_arrBigDecalRMCustomData[0]);++dwI)
					{
						char szName[16];

						sprintf(szName,"BDCD%d",dwI);
						ser.Value(szName,ref.m_arrBigDecalRMCustomData[dwI]);
					}
				}
				ser.EndGroup();
			}
	}
	else
	if(ser.IsReading())
	{
		m_nCurDecal=0;

		for(uint32 _dwI=0; _dwI<dwDecalCount; _dwI++)
			if(m_nCurDecal<DECAL_COUNT)
			{
				CDecal &ref = m_arrDecals[m_nCurDecal];

				ref.FreeRenderData();

				ser.BeginGroup("Decal");
				ser.Value("Pos",ref.m_vPos);
				ser.Value("Right",ref.m_vRight);
				ser.Value("Up",ref.m_vUp);
				ser.Value("Front",ref.m_vFront);
				ser.Value("Size",ref.m_fSize);
				ser.Value("WSPos",ref.m_vWSPos);
				ser.Value("WSSize",ref.m_fWSSize);
				ser.Value("fLifeTime",ref.m_fLifeTime);
				
				// serialize material, handle legacy decals with textureID converted to material created at runtime
				string matName( "" );
				ser.Value( "MatName", matName );
				bool isTempMat( false );
				ser.Value( "IsTempMat", isTempMat );

				ref.m_pMaterial = 0;
        if( !matName.empty() )
				{
					ref.m_pMaterial = GetMatMan()->LoadMaterial( matName.c_str(), false,true );
					if( !ref.m_pMaterial )
						Warning( "Decal material \"%s\" not found!\n", matName.c_str() );
				}

//			TODO:  IStatObj *		m_pStatObj;												// only if real 3d object is used as decal ()
				ser.Value("vAmbient",ref.m_vAmbient);
//			TODO:  IRenderNode * m_pDecalOwner;										// transformation parent object (0 means parent is the world)
        ser.Value("nRenderNodeSlotId",ref.m_ownerInfo.nRenderNodeSlotId);
        ser.Value("nRenderNodeSlotSubObjectId",ref.m_ownerInfo.nRenderNodeSlotSubObjectId);
				
				int nDecalType = eDecalType_Undefined;
				ser.Value("eDecalType",nDecalType);
				ref.m_eDecalType = (EDecal_Type)nDecalType;
				
				ser.Value("fGrowTime",ref.m_fGrowTime);
        ser.Value("fGrowTimeAlpha",ref.m_fGrowTimeAlpha);
				ser.Value("fLifeBeginTime",ref.m_fLifeBeginTime);

				// no need to to store m_arrBigDecalRMs[] as it becomes recreated

				bool bBigDecalsAreaUsed=false;

				ser.Value("bBigDecals",bBigDecalsAreaUsed);

				if(bBigDecalsAreaUsed)
				for(uint32 dwI=0;dwI<sizeof(ref.m_arrBigDecalRMCustomData)/sizeof(ref.m_arrBigDecalRMCustomData[0]);++dwI)
				{
					char szName[16];

					sprintf(szName,"BDCD%d",dwI);
					ser.Value(szName,ref.m_arrBigDecalRMCustomData[dwI]);
				}

				// m_arrBigDecalRMs[] will be created on the fly so no need load/save it

				m_arrbActiveDecals[m_nCurDecal] = bool(nDecalType != eDecalType_Undefined);

				++m_nCurDecal;
				ser.EndGroup();
			}
	}

	ser.EndGroup();
}

IMaterial* CDecalManager::GetMaterialForDecalTexture( const char* pTextureName )
{
	if( !pTextureName || *pTextureName == 0 )
		return 0;

	IMaterialManager* pMatMan( GetMatMan() );
	IMaterial* pMat( pMatMan->FindMaterial( pTextureName ) );
	if( pMat )
		return pMat;

	IMaterial* pMatSrc( pMatMan->LoadMaterial( "Materials/Decals/Default", false,true ) );
	if( pMatSrc )
	{
		IMaterial* pMatDst( pMatMan->CreateMaterial( pTextureName, pMatSrc->GetFlags() | MTL_FLAG_NON_REMOVABLE ) );
		if( pMatDst )
		{
			SShaderItem& si( pMatSrc->GetShaderItem() );

			SInputShaderResources isr( si.m_pShaderResources );
			isr.m_Textures[ EFTT_DIFFUSE ].m_Name = pTextureName;

			SShaderItem siDst( GetRenderer()->EF_LoadShaderItem( si.m_pShader->GetName(), true, 0, &isr, si.m_pShader->GetGenerationMask() ) );
			pMatDst->AssignShaderItem( siDst );

			return pMatDst;
		}
	}	

	return 0;
}

IStatObj * SDecalOwnerInfo::GetOwner(Matrix34A & objMat)
{
	if(!pRenderNode)
		return NULL;

	IStatObj * pStatObj = NULL;
	if(pStatObj = pRenderNode->GetEntityStatObj(nRenderNodeSlotId, nRenderNodeSlotSubObjectId+1, &objMat, true))
	{
	}
	else if(ICharacterInstance * pChar = pRenderNode->GetEntityCharacter(nRenderNodeSlotId, &objMat))
	{
		if(nRenderNodeSlotSubObjectId>=0)
		{
			pStatObj = pChar->GetISkeletonPose()->GetStatObjOnJoint(nRenderNodeSlotSubObjectId);
			const QuatT q = pChar->GetISkeletonPose()->GetAbsJointByID(nRenderNodeSlotSubObjectId);
			objMat = objMat*Matrix34(q);
		}
	}

	if(pStatObj && (pStatObj->GetFlags()&STATIC_OBJECT_HIDDEN))
    return NULL;

  if(pStatObj)
    if(int nMinLod = ((CStatObj*)pStatObj)->GetMinUsableLod())
      if(IStatObj *pLodObj = pStatObj->GetLodObject(nMinLod))
        pStatObj = pLodObj;

	return pStatObj;
}
